home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 January: Mac OS SDK / Dev.CD Jan 98 SDK2.toast / Development Kits (Disc 2) / QuickDraw GX / Programming Stuff / Sample Code / Printing Samples / Printer Drivers… / Scanning Generic LaserWriter / Generic LaserWriter.c < prev    next >
Encoding:
C/C++ Source or Header  |  1996-06-15  |  29.2 KB  |  1,011 lines  |  [TEXT/MPS ]

  1. /*-----------------------------------------------------------------------------
  2.  
  3.     Generic LaserWriter.c
  4.     
  5.         This part of the driver demonstrates how to handle your own 'scan'
  6.         resource processing.
  7.         
  8.        12/18/93 - dmh - Removed IIg-dependencies for b3.
  9.         9/13/93 - dmh - Updated for the b2 seed.
  10.         9/14/93 - dmh - Disabled the PostScript log which was created in the
  11.                         Extensions folder.
  12.         3/22/94 - dmh - Balanced gxInitialize override with a gxShutDown one.
  13.         6/18/94 - dmh - Added 'scan' handling support.
  14.         7/04/94 - dmh - Changed 'scan' loading so that only one fetch is done
  15.                         for either pre-scan handle regardless of how many DTPs
  16.                         are printing, and how many gxPostScriptScanStatusText
  17.                         and gxPostScriptScanPrinterText messages are processed.
  18.                         Note that this code now uses both the driver's class
  19.                         and instance contexts to store data.
  20.         7/11/94 - dmh - Changed HandleScanning and the two GXScanXXXText
  21.                         routines so that if the text becomes empty after
  22.                         prescanning, we do not consider that a return to
  23.                         "normal" status and reset our alerts.
  24.         7/12/94 - dmh - Gack!  Various uninitialized variables initialized.
  25.         6/14/96 - cn  - Updated to support Universal Interfaces 2.1.
  26.  
  27.     © 1991-1996 Apple Computer Inc.
  28.     
  29. -----------------------------------------------------------------------------*/
  30. #include "Generic LaserWriter.h"
  31.  
  32. /*    ______________________________________________________________
  33.  
  34.     DriverOpenConnection -
  35.     
  36.     This routine is an override for gxOpenConnection.  It forwards
  37.     the message and sets up our global data for the 'scan'
  38.     handling code.
  39.     
  40.     WHY THIS IS IMPORTANT:
  41.     
  42.     The 'scan' handling code will rely on our global data for
  43.     status handling state information.
  44.  
  45.     ______________________________________________________________    */
  46.  
  47. OSErr DriverOpenConnection()
  48. {
  49.     OSErr    err;
  50.  
  51. /*
  52.     Forward the gxOpenConnection message, after setting up our class
  53.     context and instance context data.
  54.  
  55.     (We'll store most of our globals in the driver's instance
  56.     context, but we'll store the 'scan' handles in the driver's
  57.     class context.  This is because all instances of our driver
  58.     can share the same 'scan' handles, so we can avoid
  59.     duplication of data and wasted memory allocation.)
  60.     
  61.     If an error occurs, clean up after ourselves.
  62. */
  63.     err = SetUpClassContextData();
  64.     if (!err) err = SetUpInstanceContextData();
  65.  
  66.     if (err)
  67.     {
  68.         DisposeClassContextData();
  69.         DisposeInstanceContextData();
  70.     }
  71.     else
  72.     {
  73.         err = Forward_GXOpenConnection();
  74.         if (err) GXCleanupOpenConnection();
  75.     }
  76.  
  77.     return err;    
  78. }
  79.  
  80.  
  81. /*    ______________________________________________________________
  82.  
  83.     DriverCleanupOpenConnection -
  84.     
  85.     This routine is an override for gxCleanupOpenConnection.  It 
  86.     removes any data we allocated in DriverOpenConnection in the
  87.     event that an error occurs and DriverCloseConnection isn't
  88.     called.  It's important to do this if you allocate data in a
  89.     gxOpenConnection override and want to toss it when the
  90.     connection closes.
  91.     
  92.     WHY THIS IS IMPORTANT:
  93.     
  94.     gxCleanupOpenConnection will be sent instead of
  95.     gxCloseConnection if an error occurs after a successful open
  96.     connection attempt.
  97.  
  98.     ______________________________________________________________    */
  99.  
  100. void DriverCleanupOpenConnection()
  101. {
  102. /*
  103.     Forward the message, then dispose of our class and instance
  104.     context data.  (We will only decrement the owner count of the
  105.     class context data if we're not the last user.)
  106. */
  107.     Forward_GXCleanupOpenConnection();
  108.     DisposeClassContextData();
  109.     DisposeInstanceContextData();
  110. }
  111.  
  112.  
  113. /*    ______________________________________________________________
  114.  
  115.     DriverCloseConnection -
  116.     
  117.     This routine is an override for gxCloseConnection.  After
  118.     forwarding the message, it frees up our global data we stored
  119.     in DriverOpenConnection.
  120.     
  121.     WHY THIS IS IMPORTANT:
  122.     
  123.     You should always clean up after yourself, so nobody will
  124.     hurt themselves tripping over your junk.
  125.  
  126.     ______________________________________________________________    */
  127.  
  128. OSErr DriverCloseConnection()
  129. {
  130.     OSErr    err;
  131. /*
  132.     Forward the message, then dispose of our class and instance
  133.     context data.  (We will only decrement the owner count of the
  134.     class context data if we're not the last user.)
  135. */
  136.     err = Forward_GXCloseConnection();
  137.     DisposeClassContextData();
  138.     DisposeInstanceContextData();
  139.  
  140.     return err;
  141. }
  142.  
  143.  
  144. /*    ______________________________________________________________
  145.  
  146.     SetUpClassContextData -
  147.     
  148.     This routine sets up the data for our class context, if it
  149.     doesn't exist.  If it already exists, we simply increment
  150.     the owner count for our data.  Otherwise, we create a handle
  151.     for our data, set its owner count to 1, and store handles to
  152.     the 'scan' resources we'll use in ScanText.
  153.     
  154.     WHY THIS IS IMPORTANT:
  155.     
  156.     By caching these resources now, we don't have to waste time
  157.     disposing and reloading them each time we scan some text.
  158.     And, since we're sharing these handles between instances of
  159.     our driver, only one load of each resource will be performed,
  160.     regardless of how many desktop printers are printing at once.
  161.     These handles are eventually disposed of in the
  162.     DriverCloseConnection routine.
  163.  
  164.     ______________________________________________________________    */
  165.  
  166. OSErr SetUpClassContextData()
  167. {
  168.     OSErr                    err = noErr;
  169.     THz                        oldZone;
  170.     DriverClassGlobalsHdl    classGlobals;
  171.  
  172. /*
  173.     Get our current class context.  If non-nil, it's a handle to our
  174.     class context data.  In that case, bump the owner count.
  175.     Otherwise, the data hasn't been set up yet, so create a handle
  176.     for our class context data, and set the owner count to 1.
  177. */
  178.     classGlobals = (DriverClassGlobalsHdl) GetMessageHandlerClassContext();
  179.     nrequire_action(classGlobals, ClassContextExists, ++(*classGlobals)->ownerCount;);
  180.  
  181.     classGlobals = (DriverClassGlobalsHdl) TempNewHandle(sizeof(DriverClassGlobals), &err);
  182.     nrequire(err, MemoryError);
  183.  
  184.     (*classGlobals)->ownerCount = 1;
  185.  
  186. /*
  187.     Get the detached 'scan' resources for gxScanStatusText and
  188.     gxScanPrinterText.  Store these handles in our global data.
  189.     Note that if a resources isn't found, then nil will be
  190.     stored as its handle's value.
  191. */
  192.     Send_GXFetchTaggedDriverData(gxPostscriptScanningType,
  193.                                  kStatusTextScanResID,
  194.                                  &(*classGlobals)->statusScanHdl);
  195.  
  196.     Send_GXFetchTaggedDriverData(gxPostscriptScanningType,
  197.                                  kPrinterTextScanResID,
  198.                                  &(*classGlobals)->printerScanHdl);
  199.  
  200. /*
  201.     Because HandleScanning requires a locked scan handle, we move
  202.     the handles high and lock them here.  Note that you could
  203.     remove this dependency in HandleScanning and DecodeScanEntry
  204.     at the expense of code readability.  Also note that MoveHHi
  205.     expects you to set the current zone to the handle's zone
  206.     before calling it.
  207. */
  208.     oldZone = GetZone();
  209.  
  210.     if ((*classGlobals)->statusScanHdl)
  211.     {
  212.         SetZone(HandleZone((*classGlobals)->statusScanHdl));
  213.         MoveHHi((*classGlobals)->statusScanHdl);
  214.         HLock((*classGlobals)->statusScanHdl);
  215.     }
  216.  
  217.     if ((*classGlobals)->printerScanHdl)
  218.     {
  219.         SetZone(HandleZone((*classGlobals)->printerScanHdl));
  220.         MoveHHi((*classGlobals)->printerScanHdl);
  221.         HLock((*classGlobals)->printerScanHdl);
  222.     }
  223.  
  224.     SetZone(oldZone);
  225.  
  226.  
  227. // Finally, store our data handle in the driver's class context.
  228.  
  229.     SetMessageHandlerClassContext(classGlobals);
  230.  
  231. MemoryError:
  232. ClassContextExists:
  233.     return err;
  234. }
  235.  
  236.  
  237. /*    ______________________________________________________________
  238.  
  239.     DisposeClassContextData -
  240.     
  241.     This routine decrements the owner count of the data we stored
  242.     in our class context, anbd disposes of the data if the owner
  243.     count goes to zero.
  244.     
  245.     WHY THIS IS IMPORTANT:
  246.     
  247.     Since several instances of our driver may be using this class
  248.     context data, we must maintain an owner count for it.  When
  249.     all instances are through using it, we dispose of the data.
  250.     To be safe, you should always use an owner count for any data
  251.     you store in a class context.  As unlikely as it may seem,
  252.     there's always the possibility that more than one instance of
  253.     your handler can be accessing the class context data at a
  254.     time, and it would be bad if you disposed of data that
  255.     another instance is still using.
  256.  
  257.     ______________________________________________________________    */
  258.  
  259. void DisposeClassContextData()
  260. {
  261.     DriverClassGlobalsHdl    classGlobals;
  262.  
  263. /*
  264.     Get our class context data and decrement the owner count.  If
  265.     the new owner count is zero, dispose of the data we stored in
  266.     SetUpClassContextData.  Otherwise, one of our instanciations
  267.     is still using the data so leave it be.
  268. */
  269.     classGlobals = (DriverClassGlobalsHdl) GetMessageHandlerClassContext();
  270.     require(classGlobals, NoGlobalsSet);
  271.  
  272.     if (--(*classGlobals)->ownerCount == 0)
  273.     {
  274.         if ((*classGlobals)->statusScanHdl)
  275.             DisposHandle((*classGlobals)->statusScanHdl);
  276.         
  277.         if ((*classGlobals)->printerScanHdl)
  278.             DisposHandle((*classGlobals)->printerScanHdl);
  279.  
  280.         DisposHandle((Handle) classGlobals);
  281.         SetMessageHandlerClassContext(nil);
  282.     }
  283.  
  284. NoGlobalsSet:;
  285. }
  286.  
  287.  
  288. /*    ______________________________________________________________
  289.  
  290.     SetUpInstanceContextData -
  291.     
  292.     This routine fills in the handle we'll store in our class
  293.     context.  This involves setting up an owner count (initially
  294.     set to 1), and storing handles to the 'scan' resources we'll
  295.     use in ScanText.
  296.     
  297.     WHY THIS IS IMPORTANT:
  298.     
  299.     By caching these resources now, we don't have to waste time
  300.     disposing and reloading them each time we scan some text.
  301.     And, since we're sharing these handles between instances of
  302.     our driver, only one load of each resource will be performed,
  303.     regardless of how many desktop printers are printing at once.
  304.     These handles are eventually disposed of in the
  305.     DriverCloseConnection routine.
  306.  
  307.     ______________________________________________________________    */
  308.  
  309. OSErr SetUpInstanceContextData()
  310. {
  311.     OSErr                        err;
  312.     DriverInstanceGlobalsHdl    instanceGlobals;
  313.  
  314. // Create and store our instance context data.
  315.  
  316.     instanceGlobals = (DriverInstanceGlobalsHdl) TempNewHandle(sizeof(DriverInstanceGlobals), &err);
  317.     nrequire(err, Failed_NewGlobals);
  318.     SetMessageHandlerInstanceContext((void *) instanceGlobals);
  319.     
  320.     (*instanceGlobals)->printStatus =
  321.         (gxStatusRecord *) NewPtrSysClear(sizeof(gxStatusRecord) +gxDefaultStatusBufferSize -1L);
  322.  
  323.     nrequire((err = MemError()), Failed_NewStatusRec);
  324.     StoreInstanceContextStrings(instanceGlobals);
  325.  
  326. Failed_NewStatusRec:
  327. Failed_NewGlobals:
  328.     return err;
  329. }
  330.  
  331.  
  332. /*    ______________________________________________________________
  333.  
  334.     StoreInstanceContextStrings -
  335.     
  336.     This routine stores the current job's application, user,
  337.     document and output printer names in our instance globals.
  338.     
  339.     WHY THIS IS IMPORTANT:
  340.     
  341.     By caching these values at gxOpenConnection time, we don't
  342.     have to repeat this work in the 'scan' handling code.
  343.  
  344.     ______________________________________________________________    */
  345.  
  346. void StoreInstanceContextStrings(DriverInstanceGlobalsHdl instanceGlobals)
  347. {
  348.     OSErr        err;
  349.     long        itemSize;
  350.     gxJobInfo    jobInfo;
  351.     gxJob        currentJob = GXGetJob();
  352.  
  353. /*
  354.     Get the current job's application, user, document
  355.     and output printer names.
  356. */
  357.     itemSize = sizeof(gxJobInfo);
  358.     err = GetCollectionItem(GXGetJobCollection(currentJob),
  359.                             gxJobTag, gxPrintingTagID,
  360.                             &itemSize, &jobInfo);
  361.  
  362. /*
  363.     No job info?  Use empty strings.
  364.     Otherwise, move the strings to our instance data.
  365. */
  366.     if (err != noErr)
  367.     {
  368.         (*instanceGlobals)->userName[0] =
  369.         (*instanceGlobals)->appName[0] =
  370.         (*instanceGlobals)->documentName[0] = (char) 0;
  371.     }
  372.     else
  373.     {
  374.         BlockMove(jobInfo.userName, (*instanceGlobals)->userName, (long) jobInfo.userName[0] +1);
  375.         BlockMove(jobInfo.appName, (*instanceGlobals)->appName, (long) jobInfo.appName[0] +1);
  376.         BlockMove(jobInfo.documentName, (*instanceGlobals)->documentName, (long) jobInfo.documentName[0] +1);
  377.     }
  378.     
  379.     GXGetPrinterName(GXGetJobOutputPrinter(currentJob), (*instanceGlobals)->printerName);
  380. }
  381.  
  382.  
  383. /*    ______________________________________________________________
  384.  
  385.     DisposeInstanceContextData -
  386.     
  387.     This routine disposes of the data we stored in our instance
  388.     context.
  389.     
  390.     WHY THIS IS IMPORTANT:
  391.     
  392.     ditto.
  393.  
  394.     ______________________________________________________________    */
  395.  
  396. void DisposeInstanceContextData()
  397. {
  398.     DriverInstanceGlobalsHdl    instanceGlobals;
  399.  
  400. // Get our instance context data.
  401.  
  402.     instanceGlobals = (DriverInstanceGlobalsHdl) GetMessageHandlerInstanceContext();
  403.     require(instanceGlobals, NoGlobalsSet);
  404.  
  405. /*
  406.     Dispose of our status record, which we allocated in
  407.     SetUpInstanceContextData.  Then, dispose of our data handle and
  408.     set our instance context to nil, to "invalidate" it.
  409. */
  410.     if ((*instanceGlobals)->printStatus)
  411.         DisposePtr((Ptr) (*instanceGlobals)->printStatus);
  412.         
  413.     DisposHandle((Handle) instanceGlobals);
  414.     SetMessageHandlerInstanceContext(nil);
  415.  
  416. NoGlobalsSet:;
  417. }
  418.  
  419.  
  420. /*    ______________________________________________________________
  421.  
  422.     DriverPostScriptScanPrinterText -
  423.     
  424.     This routine is an override for gxPostScriptScanPrinterText.
  425.     By overriding this message, we can 'scan' the passed text
  426.     ourselves before (or instead of) passing the text to GX's
  427.     default implementation.
  428.     
  429.     WHY THIS IS IMPORTANT:
  430.     
  431.     This is how we will tap into the 'scan' mechanism and put up
  432.     alerts or desktop printer messages based on our own criteria.
  433.     That means that we aren't confined to only using the 'scan'
  434.     mechanism for handling the predefined GX error conditions. 
  435.  
  436.     ______________________________________________________________    */
  437.  
  438. OSErr DriverPostScriptScanPrinterText(Handle textHdl)
  439. {
  440.     OSErr        err;
  441.     Boolean        alertedUser;
  442.  
  443. // "Pre-scan" the text.
  444.  
  445.     err = ScanText(textHdl, kScanPrinterTextType, &alertedUser);
  446.  
  447. /*
  448.     Now do the "default" scanning, unless there's an error, we
  449.     called GXAlertTheUser to post a message, or there's no text
  450.     left to scan. This keeps our last alert or message displayed
  451.     as long as we want, without the default implementation
  452.     replacing it.
  453. */
  454.     if (!err && !alertedUser && (*(long *) *textHdl != 0))
  455.         err = Forward_GXPostScriptScanPrinterText(textHdl);
  456.  
  457.     return err;
  458. }
  459.  
  460.  
  461. /*    ______________________________________________________________
  462.  
  463.     DriverPostScriptScanStatusText -
  464.     
  465.     This routine is an override for gxPostScriptScanStatusText.
  466.     By overriding this message, we can 'scan' the passed text
  467.     ourselves before (or instead of) passing the text to GX's
  468.     default implementation.
  469.     
  470.     WHY THIS IS IMPORTANT:
  471.     
  472.     This is how we will tap into the 'scan' mechanism and put up
  473.     alerts or desktop printer messages based on our own criteria.
  474.     That means that we aren't confined to only using the 'scan'
  475.     mechanism for handling the predefined GX error conditions. 
  476.  
  477.     ______________________________________________________________    */
  478.  
  479. OSErr DriverPostScriptScanStatusText(Handle textHdl)
  480. {
  481.     OSErr        err;
  482.     Boolean        alertedUser;
  483.  
  484. // "Pre-scan" the text.
  485.  
  486.     err = ScanText(textHdl, kScanStatusTextType, &alertedUser);
  487.  
  488. /*
  489.     Now do the "default" scanning, unless there's an error, we
  490.     called GXAlertTheUser to post a message, or there's no text
  491.     left to scan. This keeps our last alert or message displayed
  492.     as long as we want, without the default implementation
  493.     replacing it.
  494. */
  495.     if (!err && !alertedUser && (*(long *) *textHdl != 0))
  496.         err = Forward_GXPostScriptScanStatusText(textHdl);
  497.  
  498.     return err;
  499. }
  500.  
  501.  
  502. /*    ______________________________________________________________
  503.  
  504.     ScanText -
  505.     
  506.     This routine is called to start the scanning process for the
  507.     text passed, using the 'scan' resource indicated.  On return,
  508.     alertedUser will be set to true if GXAlertTheUser has been
  509.     called to post a message.  This indicates that the relevant
  510.     GXPostScriptScanXXXText message should not be forwarded, or
  511.     the default implementation might replace our alert or message.
  512.     
  513.     WHY THIS IS IMPORTANT:
  514.     
  515.     This code calls HandleScanning, which applies our 'scan'
  516.     resource to the text handle we're working on.
  517.  
  518.     ______________________________________________________________    */
  519.  
  520. OSErr ScanText(Handle textHdl, char scanType, Boolean *alertedUser)
  521. {
  522.     OSErr                    err = noErr;
  523.     Handle                    scanHdl;
  524.     DriverClassGlobalsHdl    classDataHdl;
  525.  
  526. /*
  527.     Initially, assume that we won't alert the user.
  528.  
  529.     Get the driver's global data, then reference the
  530.     indicated 'scan' handle.
  531. */
  532.     *alertedUser = false;
  533.     classDataHdl = (DriverClassGlobalsHdl) GetMessageHandlerClassContext();
  534.     require(classDataHdl, NoGlobalsSet);
  535.  
  536.     scanHdl = (scanType == kScanStatusTextType)?
  537.                 (*classDataHdl)->statusScanHdl: (*classDataHdl)->printerScanHdl;
  538.  
  539.     require(scanHdl, ScanHandleIsNil);
  540.  
  541. // Apply the 'scan' resource to the text.
  542.  
  543.     err = HandleScanning(textHdl, scanHdl, alertedUser);
  544.  
  545. ScanHandleIsNil:
  546. NoGlobalsSet:
  547.     return err;
  548. }
  549.  
  550.  
  551. /*    ______________________________________________________________
  552.  
  553.     HandleScanning -
  554.     
  555.     This routine is the main controlling routine of the scanning
  556.     process.  On return, alertedUser will be set to true if
  557.     GXAlertTheUser has been called to post a message.  This
  558.     indicates that the relevant GXPostScriptScanXXXText message
  559.     should not be forwarded, or the default implementation might
  560.     replace our alert or message.
  561.     
  562.     WHY THIS IS IMPORTANT:
  563.     
  564.     This code applies the settings in a 'scan' resource to the
  565.     text passed.
  566.  
  567.     ______________________________________________________________    */
  568.  
  569. OSErr HandleScanning(Handle textHdl, Handle scanHdl, Boolean *alertedUser)
  570. {
  571.     OSErr                        err = noErr;
  572.     Boolean                        found;
  573.     Str255                        searchString, replaceString;
  574.     char                        *str1Ptr, *str2Ptr;
  575.     short                        *scanPtr, priorStatusLevel, priorStatusIndex;
  576.     short                        offsetType, actionType, statusIndex, statusLevel = Normal;
  577.     long                        endOfData, searchStringLen, replaceStringLen;
  578.     gxStatusRecord                *statusPtr;
  579.     DriverInstanceGlobalsHdl    instanceStateHdl;
  580.  
  581. /*
  582.     The first long in the textHdl is the text length.  If zero,
  583.     don't scan.  Otherwise, retrieve our driver's instance data,
  584.     the previous status level and the previous 'stat' index.
  585.     Next, reset the current status level to Normal.  We will
  586.     adjust this below, based on the result of our 'scan' handling.
  587. */
  588.     require(*(long *) *textHdl, NoTextToScan);
  589.  
  590.     instanceStateHdl = (DriverInstanceGlobalsHdl) GetMessageHandlerInstanceContext();
  591.     require(instanceStateHdl, NoGlobalsSet);
  592.     statusPtr = (*instanceStateHdl)->printStatus;
  593.     require(statusPtr, NoGlobalsSet);
  594.  
  595.     priorStatusLevel = (*instanceStateHdl)->statusLevel;
  596.     priorStatusIndex = (*instanceStateHdl)->statusIndex;
  597.     (*instanceStateHdl)->statusLevel = Normal;
  598.  
  599. /*
  600.     Calculate where the last byte of the 'scan' handle is,
  601.     and move past the owner count in our 'scan' handle.
  602. */
  603.     endOfData = (long) ((*scanHdl) +GetHandleSize(scanHdl));
  604.     scanPtr = (short *) (*scanHdl +4L);
  605.  
  606. /*
  607.     While there are more 'scan' entries to process, decode
  608.     them and pass the results to Munger.
  609. */
  610.     do
  611.     {
  612.         str1Ptr = (char *) searchString;
  613.         str2Ptr = (char *) replaceString;
  614.  
  615.         err = DecodeScanEntry(&scanPtr,
  616.                               &str1Ptr, &searchStringLen,
  617.                               &str2Ptr, &replaceStringLen,
  618.                               &offsetType, &actionType,
  619.                               &statusLevel, &statusIndex);
  620.  
  621.         nrequire(err, DecodeScanEntry_Failed);
  622.  
  623.         found = MungeTheText(offsetType, textHdl,
  624.                              str1Ptr, searchStringLen,
  625.                              str2Ptr, replaceStringLen);
  626.  
  627. /*
  628.     If Munger finds a match and the 'scan' entry's action
  629.     is SimpleAction, and the status level of this entry is
  630.     greater than the current level or this is exactly the
  631.     status we found the last time we scanned, update the
  632.     current status level and 'stat' index.  This way, we
  633.     only extract the most urgent SimpleAction for processing
  634.     below.
  635. */
  636.         if (found && (actionType == SimpleAction) &&
  637.             ((statusLevel > (*instanceStateHdl)->statusLevel) ||
  638.             ((statusLevel == priorStatusLevel) && (statusIndex == priorStatusIndex))))
  639.         {
  640.             (*instanceStateHdl)->statusLevel = statusLevel;
  641.             (*instanceStateHdl)->statusIndex = statusIndex;
  642.         }
  643.     }
  644.     while ((unsigned long) scanPtr < (unsigned long) endOfData);
  645.  
  646. /*
  647.     Display or clear any alerts and messages as necessary.
  648.     
  649.     NOTE:
  650.     If you use any alerts which require special handling (such as
  651.     aborting the print job if a cancel button is pressed) you'll
  652.     need to check the status record's dialogResult and do the
  653.     appropriate thing here.
  654. */
  655.     switch ((*instanceStateHdl)->statusLevel)
  656.     {
  657.         case Normal:                // Return status to normal.
  658.             if (priorStatusLevel == NonFatalError && (*(long *) *textHdl != 0))
  659.                 err = SetAlertStatus(kPrescanStatResID, kPrinterIsReadyIndex);
  660.             break;
  661.  
  662.                                     // Non fatal error.  If this is different
  663.                                     // than the currently posted non-fatal
  664.                                     // alert (if any), clear the previous alert
  665.                                     // before posting the new one.  Indicate that
  666.         case NonFatalError:            // we have posted a message with GXAlertTheUser.
  667.  
  668.             if ((priorStatusLevel == NonFatalError) &&
  669.                 (priorStatusIndex != (*instanceStateHdl)->statusIndex))
  670.                 err = SetAlertStatus(kPrescanStatResID, kPrinterIsReadyIndex);
  671.  
  672.             if (!err)
  673.                 err = SetAlertStatus(kPrescanStatResID, (*instanceStateHdl)->statusIndex);
  674.  
  675.             *alertedUser = true;
  676.             break;
  677.         
  678.         case FatalError:            // Fatal error.  Retrieve the error code.
  679.             err = (*instanceStateHdl)->statusIndex;
  680.             break;
  681.     }
  682.  
  683. DecodeScanEntry_Failed:
  684. NoGlobalsSet:
  685. NoTextToScan:
  686.     return err;
  687. }
  688.  
  689.  
  690. /*    ______________________________________________________________
  691.  
  692.     DecodeScanEntry -
  693.     
  694.     This routine parses one entry in a 'scan' resource and
  695.     determines what it all means.
  696.  
  697.     WHY THIS IS IMPORTANT:
  698.     
  699.     This is the code that turns 'scan' handle mush into coherant
  700.     data.
  701.  
  702.     ______________________________________________________________    */
  703.  
  704. OSErr DecodeScanEntry(short **scanPtrHolder,
  705.                       char **str1Ptr, long *str1Len,
  706.                       char **str2Ptr, long *str2Len,
  707.                       short *offsetType, short *actionType,
  708.                       short *statusLevel, short *statusIndex)
  709. {
  710.     OSErr                        err = noErr;
  711.     DriverInstanceGlobalsHdl    instanceStateHdl;
  712.     long                        *whichLen;
  713.     char                        parsePass, *whichString;
  714.     short                        stringType, *scanPtr = *scanPtrHolder;
  715.  
  716. // Retrieve our globals.
  717.  
  718.     instanceStateHdl = (DriverInstanceGlobalsHdl) GetMessageHandlerInstanceContext();
  719.  
  720.     for (parsePass = 0; parsePass < 2; parsePass++)
  721.     {
  722. /*
  723.     We make two passes through this loop, extracting the
  724.     search string entry on the first pass, and the replacement
  725.     string entry on the second.
  726. */
  727.         whichLen = (parsePass == 0)? str1Len: str2Len;
  728.         whichString = (parsePass == 0)? *str1Ptr: *str2Ptr;
  729.  
  730. // Get the entry's string type and move past the word it occupied.
  731.  
  732.         stringType = *scanPtr;
  733.         ++scanPtr;
  734.  
  735. // Now extract any auxiliary data for this string type.
  736.         
  737.         switch (stringType)
  738.         {
  739. /*
  740.     SimpleScan entries have a length word followed by
  741.     word-aligned string data.  For these, get the length,
  742.     move past that word, BlockMove the characters to our
  743.     string, and update our scanPtr to the word after the
  744.     last character (remember, it's word-aligned).
  745. */
  746.             case SimpleScan:
  747.                 *whichLen = *scanPtr;
  748.                 ++scanPtr;
  749.                 BlockMove(scanPtr, whichString, (long) *whichLen);
  750.                 scanPtr = (short *) (((char *) scanPtr) + *whichLen + (*whichLen & 1L));
  751.                 break;
  752.  
  753. /*
  754.     UserNameScan, DocumentNameScan, and PrinterNameScan
  755.     entries have no extra data, but we need to retrieve
  756.     the requested information.  We cached these strings
  757.     earlier, so we just need to store the lengths and
  758.     BlockMove the characters.  Note that there's no extra
  759.     data to move past, so we don't update the scanPtr.
  760. */
  761.             case UserNameScan:
  762.                 *whichLen = (*instanceStateHdl)->userName[0];
  763.                 BlockMove(&(*instanceStateHdl)->userName[1], whichString, *whichLen);
  764.                 break;
  765.  
  766.             case DocumentNameScan:
  767.                 *whichLen = (*instanceStateHdl)->documentName[0];
  768.                 BlockMove(&(*instanceStateHdl)->documentName[1], whichString, *whichLen);
  769.                 break;
  770.  
  771.             case PrinterNameScan:
  772.                 *whichLen = (*instanceStateHdl)->printerName[0];
  773.                 BlockMove(&(*instanceStateHdl)->printerName[1], whichString, *whichLen);
  774.                 break;
  775.  
  776. /*
  777.     NilPtrScan entries have a length word which indicates a
  778.     a length to pass to Munger along with the nil pointer.
  779.     For these, get the length, move past that word, and set
  780.     the appropriate string pointer to nil.
  781. */
  782.             case NilPtrScan:
  783.                 *whichLen = *scanPtr;
  784.                 ++scanPtr;
  785.  
  786.                 if (parsePass == 0)
  787.                     *str1Ptr = nil;
  788.                 else
  789.                     *str2Ptr = nil;
  790.  
  791.                 break;
  792.         }
  793.     }
  794.     
  795. //    Get the entry's offset and action types, and move past those words.
  796.     
  797.     *offsetType = *scanPtr;
  798.     ++scanPtr;
  799.     *actionType = *scanPtr;
  800.     ++scanPtr;
  801.     
  802. /*
  803.     If the action type is SimpleAction, get the status level,
  804.     (Normal, NonFatalError, or FatalError), move past that
  805.     word, get the 'stat' index to use, and move past that
  806.     word as well.
  807. */
  808.     if (*actionType == SimpleAction)
  809.     {
  810.         *statusLevel = *scanPtr;
  811.         ++scanPtr;
  812.         *statusIndex = *scanPtr;
  813.         ++scanPtr;
  814.     }
  815.     else
  816.         *statusLevel = *statusIndex = 0;
  817.  
  818. // Update the passed 'scan' pointer, and return any errors.
  819.  
  820.     *scanPtrHolder = scanPtr;
  821.     return err;
  822. }
  823.  
  824.  
  825. /*    ______________________________________________________________
  826.  
  827.     MungeTheText -
  828.     
  829.     This routine finds and replaces text in our text handle, based
  830.     on the results from our 'scan' parsing.  It also returns true
  831.     if the search string was found at least once.
  832.     
  833.     WHY THIS IS IMPORTANT:
  834.     
  835.     This code calls Munger to change the wording in our text
  836.     handle, which may end up in the desktop printer window.  It
  837.     also tells us if keywords we're watching for in our 'scan'
  838.     resource (like "cover open") are in the text handle.
  839.  
  840.     Briefly, text is munged as follows:
  841.     
  842.     SimpleOffset -            Find or Replace 1st occurance of the
  843.                             ptr1 string.
  844.  
  845.     SameAsPreviousOffset -    Insert the ptr2 text before the 1st
  846.                             occurance of the ptr1 string.
  847.  
  848.     ReturnedOffset -        Insert the ptr2 text after the first
  849.                             occurance of the ptr1 string.
  850.     SimpleRepeat,
  851.     SameAsPreviousRepeat,
  852.     ReturnedRepeat -        Same as the above methods but apply
  853.                             repeatedly to entire text handle.
  854.  
  855.     Now, here's what Munger will do with the data it's passed, in
  856.     the context of the above rules. (This Munger text comes from
  857.     Inside Mac.)
  858.  
  859.     •    If ptr1 is NIL, the substring of length len1 starting at
  860.         the given offset is replaced by the replacement string. If
  861.         len1 is negative, the substring from the given offset to
  862.         the end of the destination string is replaced by the
  863.         replacement string. In either case, Munger returns the
  864.         offset of the first byte past where the replacement
  865.         occurred.
  866.  
  867.     •    If len1 is 0, (ptr2,len2) is simply inserted at the given
  868.         offset; no text is replaced. Munger returns the offset of
  869.         the first byte past where the insertion occurred.
  870.  
  871.     •    If ptr2 is NIL, Munger returns the offset at which the
  872.         target string was found. The destination string isn’t
  873.         changed.
  874.  
  875.     •    If len2 is 0 (and ptr2 is not NIL), the target string is
  876.         deleted rather than replaced (since the replacement string
  877.         is empty). Munger returns the offset at which the deletion
  878.         occurred.
  879.  
  880.     ______________________________________________________________    */
  881.  
  882. Boolean    MungeTheText(short offsetType, Handle textHdl,
  883.                      void *ptr1, long len1,
  884.                      void *ptr2, long len2)
  885. {
  886.     long        handleSize, offset;
  887.     Boolean        found = false;
  888.  
  889. /*
  890.     The first longword of the text handle is the length of the
  891.     text in it.  Set the handle to that size (plus the length
  892.     longword).  Set our offset into the text handle to point
  893.     after the length longword.
  894. */
  895.     handleSize = *(long *) *textHdl;
  896.     SetHandleSize(textHdl, handleSize +4);
  897.     offset = 4;
  898.     if (handleSize <= 0) return false;
  899.  
  900.  
  901. /*
  902.     Based on the offsetType passed, do the appropriate munging.
  903.     See the description in the function header for more info.
  904. */
  905.     switch (offsetType)
  906.     {
  907.         case SimpleOffset:            // replace the old occurances.
  908.         case SimpleRepeat:
  909.             do
  910.             {
  911.                 offset = Munger(textHdl, offset, ptr1, len1, ptr2, len2);
  912.                 if (offset > 0) found = true;
  913.                 if (offset >= GetHandleSize(textHdl))
  914.                     offset = -1;
  915.             }
  916.             while ((offsetType == SimpleRepeat) && (offset > 0));
  917.             break;
  918.             
  919.         case SameAsPreviousOffset:    // insert just before the old occurances.
  920.         case SameAsPreviousRepeat:
  921.             do
  922.             {
  923.                 offset = Munger(textHdl, offset, ptr1, len1, nil, 0);
  924.                 if (offset > 0)
  925.                 {
  926.                     found = true;
  927.                     offset = Munger(textHdl, offset, nil, 0, ptr2, len2);
  928.                     offset += len1;
  929.                     if (offset >= GetHandleSize(textHdl))
  930.                         offset = -1;
  931.                 }
  932.             }
  933.             while ((offsetType == SameAsPreviousRepeat) && (offset > 0));
  934.             break;
  935.  
  936.         case ReturnedOffset:        // insert just after the old occurances.
  937.         case ReturnedRepeat:
  938.             do
  939.             {
  940.                 offset = Munger(textHdl, offset, ptr1, len1, nil, 0);
  941.                 if (offset > 0)
  942.                 {
  943.                     found = true;
  944.                     offset += len1;
  945.                     offset = Munger(textHdl, offset, nil, 0, ptr2, len2);
  946.                     if (offset > GetHandleSize(textHdl))
  947.                         offset = -1;
  948.                 }
  949.             }
  950.             while ((offsetType == ReturnedRepeat) && (offset > 0));
  951.             break;
  952.     }
  953.  
  954. /*
  955.     If we found a match, (and might have changed the text), reset
  956.     the text size longword in the text handle.
  957. */
  958.     if (found)
  959.         *(long *) *textHdl = GetHandleSize(textHdl) -4;
  960.  
  961.     return found;
  962. }
  963.  
  964.  
  965. /*    ______________________________________________________________
  966.  
  967.     SetAlertStatus -
  968.     
  969.     This routine calls GXAlertTheUser to display an alert, desktop
  970.     printer message, to clear the alert status (by passing the
  971.     printerReady code to GXAlertTheUser.
  972.  
  973.     WHY THIS IS IMPORTANT:
  974.     
  975.     This is the code that puts up and takes down dialogs, and
  976.     clears the printer status based on our 'scan' handling results.
  977.  
  978.     ______________________________________________________________    */
  979.  
  980. OSErr SetAlertStatus(short statResourceID, short statResourceIndex)
  981. {
  982.     OSErr                        err = noErr;
  983.     gxStatusRecord                 *statusPtr;
  984.     DriverInstanceGlobalsHdl    instanceStateHdl;
  985.  
  986. // Retrieve our global data.
  987.  
  988.     instanceStateHdl = (DriverInstanceGlobalsHdl) GetMessageHandlerInstanceContext();
  989.     require(instanceStateHdl, NoGlobalsSet);
  990.     statusPtr = (*instanceStateHdl)->printStatus;
  991.     require(statusPtr, NoGlobalsSet);
  992.  
  993. /*
  994.     Simply alert or update the dtp window based on what's
  995.     in 'stat' ID = statResourceID, index = statResourceIndex.
  996. */
  997.     statusPtr->statusType = 0;
  998.     statusPtr->statusId = 0;
  999.     statusPtr->statusAlertId = 0;
  1000.     statusPtr->statusOwner = kCreator;
  1001.     statusPtr->statResId = statResourceID;
  1002.     statusPtr->statResIndex = statResourceIndex;
  1003.     statusPtr->dialogResult = 0;
  1004.     statusPtr->bufferLen = gxDefaultStatusBufferSize;
  1005.  
  1006.     err = GXAlertTheUser(statusPtr);
  1007.  
  1008. NoGlobalsSet:
  1009.     return err;
  1010. }
  1011.